
package com.neo.tiny.oauth.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.annotations.VisibleForTesting;
import com.neo.tiny.oauth.entity.SysOauth2ApproveDO;
import com.neo.tiny.oauth.entity.SysOauth2ClientDO;
import com.neo.tiny.oauth.mapper.SysOauth2ApproveMapper;
import com.neo.tiny.oauth.service.SysOauth2ApproveService;
import com.neo.tiny.oauth.service.SysOauth2ClientService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: OAuth2 批准
 * @Author: yqz
 * @CreateDate: 2022-11-11 17:18:47
 */
@Service
@AllArgsConstructor
public class SysOauth2ApproveServiceImpl extends ServiceImpl<SysOauth2ApproveMapper, SysOauth2ApproveDO> implements SysOauth2ApproveService {
    /**
     * 批准的过期时间，默认 30 天,单位：秒
     */
    private static final Integer TIMEOUT = 30 * 24 * 60 * 60;


    private final SysOauth2ClientService oauth2ClientService;

    private final SysOauth2ApproveMapper oauth2ApproveMapper;

    @Override
    public boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection<String> requestedScopes) {

        // 1、根据client的自动授权计算，如果scopes都在自动授权内，则返回 true 通过
        SysOauth2ClientDO oauthClient = oauth2ClientService.validOauthClient(clientId);
        Assert.notNull(oauthClient, "无该客户端");
        if (CollUtil.containsAll(oauthClient.getAutoApproveScopes(), requestedScopes)) {

            // gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store.
            LocalDateTime expireTime = LocalDateTime.now().plusSeconds(TIMEOUT);
            for (String scope : requestedScopes) {
                saveApprove(userId, userType, clientId, scope, true, expireTime);
            }
            return true;
        }

        // 2、算上用户已经批准的授权，如果scopes都包含，则返回true
        List<SysOauth2ApproveDO> approveList = getApproveList(userId, userType, clientId);
        Set<String> scopes = approveList.stream()
                .filter(SysOauth2ApproveDO::getApproved)
                .map(SysOauth2ApproveDO::getScope)
                .filter(Objects::nonNull).collect(Collectors.toSet());

        return CollUtil.containsAll(scopes, requestedScopes);
    }

    @Override
    public boolean updateAfterApproval(Long userId, Integer userType,
                                       String clientId, Map<String, Boolean> requestedScopes) {
        // 如果 requestedScopes 为空，说明没有要求，则返回 true 通过
        if (CollUtil.isEmpty(requestedScopes)) {
            return true;
        }

        // 更新批准的信息, 需要至少有一个同意
        boolean success = false;
        LocalDateTime expireTime = LocalDateTime.now().plusSeconds(TIMEOUT);
        for (Map.Entry<String, Boolean> entry : requestedScopes.entrySet()) {
            if (entry.getValue()) {
                success = true;
            }
            saveApprove(userId, userType, clientId, entry.getKey(), entry.getValue(), expireTime);
        }
        return success;
    }

    @Override
    public List<SysOauth2ApproveDO> getApproveList(Long userId, Integer userType, String clientId) {

        List<SysOauth2ApproveDO> approveList = oauth2ApproveMapper.selectListByUserIdAndUserTypeAndClientId(userId, userType, clientId);

        approveList.removeIf(approve -> approve.getExpiresTime().isBefore(LocalDateTime.now()));

        return approveList;
    }


    @VisibleForTesting
    public void saveApprove(Long userId, Integer userType, String clientId,
                     String scope, Boolean approved, LocalDateTime expireTime) {
        // 先更新
        SysOauth2ApproveDO approveDO = new SysOauth2ApproveDO();
        approveDO.setUserId(userId);
        approveDO.setUserType(userType);
        approveDO.setClientId(clientId);
        approveDO.setScope(scope);
        approveDO.setApproved(approved);
        approveDO.setExpiresTime(expireTime);
        if (oauth2ApproveMapper.update(approveDO) == 1) {
            return;
        }
        // 失败，则说明不存在，进行更新
        oauth2ApproveMapper.insert(approveDO);
    }

}
